#include "path.hpp"
namespace chen {
PathShader::PathShader(const Scene * sce, const Camera * cam, Sampler * sampler, int split, int nbs, int nls) :
    SamplerShader(sce, cam, sampler), ray_split(split), nbsdf_samples(nbs), nlight_samples(nls) {}

void PathShader::prepare () {
    LOG(INFO) << "[Path Shader] using path shader.";
}

Spectrum PathShader::Li (const RayDifferential & r) const {
    Point2 * samples = sampler->gen2DArray(ray_split);
    Spectrum acc(0);
    for(int i = 0; i<ray_split; i++) // 单像素超采样
        acc += LiRecursive(r.exact(samples[i]), 1.0);
    return acc/ray_split;
}

/**
 * q是俄罗斯轮盘参数
 * 衰减默认0.9
 * */
Spectrum PathShader::LiRecursive(const Ray & r, Float q, int dep) const {
    if(rand01() > q || dep > MAXDEP) return Spectrum(0); // 俄罗斯轮盘赌
    SurfInteract si;
    Spectrum ret = 0;
    // 和任何物体都没有交点，返回背景辐射
    if(!scene->castRay(r, &si)) {
        for(auto li = scene->lights.begin(); li != scene->lights.end(); li++)
            ret += (*li)->Le(r);
        return ret;
    }
    const BSDF * bsdf = si.hit->material->bsdf;
    int cnbsdf_samples = dep == 0 ? nbsdf_samples : 1;
    int cnlight_samples = dep == 0 ? nlight_samples : 1;
    // 开始光线追踪
    if(hasFlag(bsdf->flags, FBSDF_DELTA)) { // BSDF函数为Delta分布，不需要对光源进行采样，只需要对BSDF函数进行采样
        //puts("sample delta(impossible)");
        //sampler->startSample(); // 采样器重启，这是一个究极愚蠢的行为
        Point2 * bsdf_samples = sampler->gen2DArray(cnbsdf_samples); // 生成随机点列
        for(int i = 0; i<cnbsdf_samples; i++) {
            Point2 u = bsdf_samples[i];
            Vector3 wi;
            Float pdf;
            Spectrum fac = si.sampleBSDF(si.wo, u, &wi, &pdf); // 将具体的采样方式交给bsdf进行实现，此处pdf便于实现重要性采样
            ret += PathShader::LiRecursive(si.spawnRay(wi), q*0.9, dep+1)
                *abs(dot(wi.normalized(), si.n))*(fac/pdf);  // 累加贡献
        }
        ret = ret/cnbsdf_samples;
        delete bsdf_samples;
    } else {
        // sampler->startSample(); // 开始新一轮采样，这是一个究极愚蠢的行为
        Point2 * bsdf_samples = sampler->gen2DArray(cnbsdf_samples);
        Point2 * light_samples = sampler->gen2DArray(cnlight_samples);
        Spectrum ret1 = 0, ret2 = 0;
        Float phnumu1 = 0, phnumu2 = 0; // 幂启发式合并分母
        for(int i = 0; i<cnbsdf_samples; i++) {
            Point2 u = bsdf_samples[i];
            Vector3 wi;
            Float pdf;
            Spectrum fac = si.sampleBSDF(si.wo, u, &wi, &pdf); // 将具体的采样方式交给bsdf进行实现，此处pdf便于实现重要性采样
            ret1 += (
                PathShader::LiRecursive(si.spawnRay(wi), q*0.9, dep+1)
                *abs(dot(wi.normalized(), si.n))*fac/pdf
            )*pw2(cnbsdf_samples*pdf);  // 计算幂启发式合并因子
            phnumu1 += pw2(cnbsdf_samples*pdf);
        }
        // 计算面光源的贡献
        if(this->scene->arealights.size()) 
            for(int i = 0; i<cnlight_samples; i++) {
                //使用简单的随机抽取光源法
                const AreaLight* l = this->scene->randomPickAreaLight();
                Point2 u = light_samples[i];
                Vector3 wi;
                bool occluded;
                Float pdf;
                Spectrum lit = l->sampleLi(si, u, &wi, &pdf, &occluded); // 将具体的采样方式交给面光源进行实现
                if(!occluded) {
                    ret2 += lit*si.fBSDF(wi)*abs(dot(wi.normalized(), si.n))/pdf*pw2(cnlight_samples*pdf); // 计算幂启发式合并分子
                    phnumu2 += pw2(cnlight_samples*pdf);  // 累加贡献
                }
            }
        // 计算其它光源的贡献
        for(auto it = this->scene->globallights.begin(); it!=this->scene->globallights.end(); it++) { 
            bool occluded;
            Vector3 wi;
            Spectrum lit = (*it)->Li(si, &wi, &occluded);
            if(!occluded) ret += lit*si.fBSDF(wi);
        }
        // 多重采样合并阶段
        ret += (ret1/cnbsdf_samples+ret2/cnlight_samples)/(phnumu1+phnumu2);
        delete bsdf_samples;
        delete light_samples;
    }
    // 加上自发光贡献，并使用俄罗斯轮盘计算
    return (ret+si.Le())/q;
}
}